﻿#pragma once
#pragma region qt_headers
#include <QMenu>
#include <QMutex>
#include <QPointer>
#include <QString>

#if defined(QT_DEBUG)
#include <QElapsedTimer>
#endif
#pragma endregion qt_headers

#include <CefViewBrowserClient.h>
#include <CefViewBrowserClientDelegate.h>

#include "CCefClientDelegate.h"
#include "QCefContextPrivate.h"
#include "QCefWindow.h"
#include "utils/MenuBuilder.h"

#include <QCefView.h>

#include "tests/cefclient/browser/osr_dragdrop_win.h"

class CSize;
class CPoint;
class CRect;

class CSize : public SIZE
{
public:
    // Constructors
    CSize()
    {
        cx = 0;
        cy = 0;
    }

    CSize(int initCX, int initCY)
    {
        cx = initCX;
        cy = initCY;
    }

    CSize(SIZE initSize)
    {
        *(SIZE*)this = initSize;
    }

    CSize(POINT initPt)
    {
        *(POINT*)this = initPt;
    }

    CSize(DWORD dwSize)
    {
        cx = (short)LOWORD(dwSize);
        cy = (short)HIWORD(dwSize);
    }

    // Operations
    BOOL operator ==(SIZE size) const
    {
        return (cx == size.cx && cy == size.cy);
    }

    BOOL operator !=(SIZE size) const
    {
        return (cx != size.cx || cy != size.cy);
    }

    void operator +=(SIZE size)
    {
        cx += size.cx;
        cy += size.cy;
    }

    void operator -=(SIZE size)
    {
        cx -= size.cx;
        cy -= size.cy;
    }

    void SetSize(int CX, int CY)
    {
        cx = CX;
        cy = CY;
    }

    // Operators returning CSize values
    CSize operator +(SIZE size) const
    {
        return CSize(cx + size.cx, cy + size.cy);
    }

    CSize operator -(SIZE size) const
    {
        return CSize(cx - size.cx, cy - size.cy);
    }

    CSize operator -() const
    {
        return CSize(-cx, -cy);
    }

    // Operators returning CPoint values
    CPoint operator +(POINT point) const;
    CPoint operator -(POINT point) const;

    // Operators returning CRect values
    CRect operator +(const RECT* lpRect) const;
    CRect operator -(const RECT* lpRect) const;
};

class CPoint : public POINT
{
public:
    // Constructors
    CPoint()
    {
        x = 0;
        y = 0;
    }

    CPoint(int initX, int initY)
    {
        x = initX;
        y = initY;
    }

    CPoint(POINT initPt)
    {
        *(POINT*)this = initPt;
    }

    CPoint(SIZE initSize)
    {
        *(SIZE*)this = initSize;
    }

    CPoint(DWORD dwPoint)
    {
        x = (short)LOWORD(dwPoint);
        y = (short)HIWORD(dwPoint);
    }

    // Operations
    void Offset(int xOffset, int yOffset)
    {
        x += xOffset;
        y += yOffset;
    }

    void Offset(POINT point)
    {
        x += point.x;
        y += point.y;
    }

    void Offset(SIZE size)
    {
        x += size.cx;
        y += size.cy;
    }

    BOOL operator ==(POINT point) const
    {
        return (x == point.x && y == point.y);
    }

    BOOL operator !=(POINT point) const
    {
        return (x != point.x || y != point.y);
    }

    void operator +=(SIZE size)
    {
        x += size.cx;
        y += size.cy;
    }

    void operator -=(SIZE size)
    {
        x -= size.cx;
        y -= size.cy;
    }

    void operator +=(POINT point)
    {
        x += point.x;
        y += point.y;
    }

    void operator -=(POINT point)
    {
        x -= point.x;
        y -= point.y;
    }

    void SetPoint(int X, int Y)
    {
        x = X;
        y = Y;
    }

    // Operators returning CPoint values
    CPoint operator +(SIZE size) const
    {
        return CPoint(x + size.cx, y + size.cy);
    }

    CPoint operator -(SIZE size) const
    {
        return CPoint(x - size.cx, y - size.cy);
    }

    CPoint operator -() const
    {
        return CPoint(-x, -y);
    }

    CPoint operator +(POINT point) const
    {
        return CPoint(x + point.x, y + point.y);
    }

    // Operators returning CSize values
    CSize operator -(POINT point) const
    {
        return CSize(x - point.x, y - point.y);
    }

    // Operators returning CRect values
    CRect operator +(const RECT* lpRect) const;
    CRect operator -(const RECT* lpRect) const;
};

class CRect : public RECT
{
public:
    // Constructors
    CRect()
    {
        left = 0;
        top = 0;
        right = 0;
        bottom = 0;
    }

    CRect(int l, int t, int r, int b)
    {
        left = l;
        top = t;
        right = r;
        bottom = b;
    }

    CRect(const RECT& srcRect)
    {
        ::CopyRect(this, &srcRect);
    }

    CRect(LPCRECT lpSrcRect)
    {
        ::CopyRect(this, lpSrcRect);
    }

    CRect(POINT point, SIZE size)
    {
        right = (left = point.x) + size.cx;
        bottom = (top = point.y) + size.cy;
    }

    CRect(POINT topLeft, POINT bottomRight)
    {
        left = topLeft.x;
        top = topLeft.y;
        right = bottomRight.x;
        bottom = bottomRight.y;
    }

    // Attributes (in addition to RECT members)
    int Width() const
    {
        return right - left;
    }

    int Height() const
    {
        return bottom - top;
    }

    CSize Size() const
    {
        return CSize(right - left, bottom - top);
    }

    CPoint& TopLeft()
    {
        return *((CPoint*)this);
    }

    CPoint& BottomRight()
    {
        return *((CPoint*)this + 1);
    }

    const CPoint& TopLeft() const
    {
        return *((CPoint*)this);
    }

    const CPoint& BottomRight() const
    {
        return *((CPoint*)this + 1);
    }

    CPoint CenterPoint() const
    {
        return CPoint((left + right) / 2, (top + bottom) / 2);
    }

    // convert between CRect and LPRECT/LPCRECT (no need for &)
    operator LPRECT()
    {
        return this;
    }

    operator LPCRECT() const
    {
        return this;
    }

    BOOL IsRectEmpty() const
    {
        return ::IsRectEmpty(this);
    }

    BOOL IsRectNull() const
    {
        return (left == 0 && right == 0 && top == 0 && bottom == 0);
    }

    BOOL PtInRect(POINT point) const
    {
        return ::PtInRect(this, point);
    }

    // Operations
    void SetRect(int x1, int y1, int x2, int y2)
    {
        ::SetRect(this, x1, y1, x2, y2);
    }

    void SetRect(POINT topLeft, POINT bottomRight)
    {
        ::SetRect(this, topLeft.x, topLeft.y, bottomRight.x, bottomRight.y);
    }

    void SetRectEmpty()
    {
        ::SetRectEmpty(this);
    }

    void CopyRect(LPCRECT lpSrcRect)
    {
        ::CopyRect(this, lpSrcRect);
    }

    BOOL EqualRect(LPCRECT lpRect) const
    {
        return ::EqualRect(this, lpRect);
    }

    void InflateRect(int x, int y)
    {
        ::InflateRect(this, x, y);
    }

    void InflateRect(SIZE size)
    {
        ::InflateRect(this, size.cx, size.cy);
    }

    void InflateRect(LPCRECT lpRect)
    {
        left -= lpRect->left;
        top -= lpRect->top;
        right += lpRect->right;
        bottom += lpRect->bottom;
    }

    void InflateRect(int l, int t, int r, int b)
    {
        left -= l;
        top -= t;
        right += r;
        bottom += b;
    }

    void DeflateRect(int x, int y)
    {
        ::InflateRect(this, -x, -y);
    }

    void DeflateRect(SIZE size)
    {
        ::InflateRect(this, -size.cx, -size.cy);
    }

    void DeflateRect(LPCRECT lpRect)
    {
        left += lpRect->left;
        top += lpRect->top;
        right -= lpRect->right;
        bottom -= lpRect->bottom;
    }

    void DeflateRect(int l, int t, int r, int b)
    {
        left += l;
        top += t;
        right -= r;
        bottom -= b;
    }

    void OffsetRect(int x, int y)
    {
        ::OffsetRect(this, x, y);
    }
    void OffsetRect(SIZE size)
    {
        ::OffsetRect(this, size.cx, size.cy);
    }

    void OffsetRect(POINT point)
    {
        ::OffsetRect(this, point.x, point.y);
    }

    void NormalizeRect()
    {
        int nTemp;
        if (left > right)
        {
            nTemp = left;
            left = right;
            right = nTemp;
        }
        if (top > bottom)
        {
            nTemp = top;
            top = bottom;
            bottom = nTemp;
        }
    }

    // absolute position of rectangle
    void MoveToY(int y)
    {
        bottom = Height() + y;
        top = y;
    }

    void MoveToX(int x)
    {
        right = Width() + x;
        left = x;
    }

    void MoveToXY(int x, int y)
    {
        MoveToX(x);
        MoveToY(y);
    }

    void MoveToXY(POINT pt)
    {
        MoveToX(pt.x);
        MoveToY(pt.y);
    }

    // operations that fill '*this' with result
    BOOL IntersectRect(LPCRECT lpRect1, LPCRECT lpRect2)
    {
        return ::IntersectRect(this, lpRect1, lpRect2);
    }

    BOOL UnionRect(LPCRECT lpRect1, LPCRECT lpRect2)
    {
        return ::UnionRect(this, lpRect1, lpRect2);
    }

    BOOL SubtractRect(LPCRECT lpRectSrc1, LPCRECT lpRectSrc2)
    {
        return ::SubtractRect(this, lpRectSrc1, lpRectSrc2);
    }

    // Additional Operations
    void operator =(const RECT& srcRect)
    {
        ::CopyRect(this, &srcRect);
    }

    BOOL operator ==(const RECT& rect) const
    {
        return ::EqualRect(this, &rect);
    }

    BOOL operator !=(const RECT& rect) const
    {
        return !::EqualRect(this, &rect);
    }

    void operator +=(POINT point)
    {
        ::OffsetRect(this, point.x, point.y);
    }

    void operator +=(SIZE size)
    {
        ::OffsetRect(this, size.cx, size.cy);
    }

    void operator +=(LPCRECT lpRect)
    {
        InflateRect(lpRect);
    }

    void operator -=(POINT point)
    {
        ::OffsetRect(this, -point.x, -point.y);
    }

    void operator -=(SIZE size)
    {
        ::OffsetRect(this, -size.cx, -size.cy);
    }

    void operator -=(LPCRECT lpRect)
    {
        DeflateRect(lpRect);
    }

    void operator &=(const RECT& rect)
    {
        ::IntersectRect(this, this, &rect);
    }

    void operator |=(const RECT& rect)
    {
        ::UnionRect(this, this, &rect);
    }

    // Operators returning CRect values
    CRect operator +(POINT pt) const
    {
        CRect rect(*this);
        ::OffsetRect(&rect, pt.x, pt.y);
        return rect;
    }

    CRect operator -(POINT pt) const
    {
        CRect rect(*this);
        ::OffsetRect(&rect, -pt.x, -pt.y);
        return rect;
    }

    CRect operator +(LPCRECT lpRect) const
    {
        CRect rect(this);
        rect.InflateRect(lpRect);
        return rect;
    }

    CRect operator +(SIZE size) const
    {
        CRect rect(*this);
        ::OffsetRect(&rect, size.cx, size.cy);
        return rect;
    }

    CRect operator -(SIZE size) const
    {
        CRect rect(*this);
        ::OffsetRect(&rect, -size.cx, -size.cy);
        return rect;
    }

    CRect operator -(LPCRECT lpRect) const
    {
        CRect rect(this);
        rect.DeflateRect(lpRect);
        return rect;
    }

    CRect operator &(const RECT& rect2) const
    {
        CRect rect;
        ::IntersectRect(&rect, this, &rect2);
        return rect;
    }

    CRect operator |(const RECT& rect2) const
    {
        CRect rect;
        ::UnionRect(&rect, this, &rect2);
        return rect;
    }

    CRect MulDiv(int nMultiplier, int nDivisor) const
    {
        return CRect(
                ::MulDiv(left, nMultiplier, nDivisor),
                ::MulDiv(top, nMultiplier, nDivisor),
                ::MulDiv(right, nMultiplier, nDivisor),
                ::MulDiv(bottom, nMultiplier, nDivisor));
    }
};

typedef cef_drag_operations_mask_t DragOperationsMask;

class QCefViewPrivate : public QObject
        , public client::OsrDragEvents
{
  Q_OBJECT
  Q_DECLARE_PUBLIC(QCefView)
  QCefView* q_ptr;

  friend class CCefClientDelegate;

private:
  static QSet<QCefViewPrivate*> sLiveInstances;

public:
  /// <summary>
  ///
  /// </summary>
  static void destroyAllInstance();

  /// <summary>
  ///
  /// </summary>
  CComPtr<client::DropTargetWin> m_pDropTarget;

  /// <summary>
  ///
  /// </summary>
  CefRenderHandler::DragOperation m_pCurDropOp;

  /// <summary>
  ///
  /// </summary>
  WId m_wid = NULL;

  /// <summary>
  ///
  /// </summary>
  float device_scale_factor_ = 1.25;

  /// <summary>
  ///
  /// </summary>
  bool disablePopupContextMenu_ = false;

  /// <summary>
  ///
  /// </summary>
  bool enableDragAndDrop_ = false;

  /// <summary>
  ///
  /// </summary>
  QCefContextPrivate* pContextPrivate_ = nullptr;

  /// <summary>
  ///
  /// </summary>
  CefRefPtr<CefViewBrowserClient> pClient_ = nullptr;

  /// <summary>
  ///
  /// </summary>
  CCefClientDelegate::RefPtr pClientDelegate_ = nullptr;

  /// <summary>
  ///
  /// </summary>
  CefRefPtr<CefBrowser> pCefBrowser_ = nullptr;

  /// <summary>
  ///
  /// </summary>
  bool isOSRModeEnabled_ = false;

  /// <summary>
  /// Offscreen rendering private data
  /// </summary>
  struct OsrPrivateData
  {
    /// <summary>
    ///
    /// </summary>
    bool transparentPaintingEnabled = false;

    /// <summary>
    ///
    /// </summary>
    bool showPopup_ = false;

    /// <summary>
    ///
    /// </summary>
    QRect qPopupRect_;

    /// <summary>
    ///
    /// </summary>
    QRect qImeCursorRect_;

    /// <summary>
    ///
    /// </summary>
    QMutex qViewPaintLock_;

    /// <summary>
    ///
    /// </summary>
    QImage qCefViewFrame_;

    /// <summary>
    ///
    /// </summary>
    QMutex qPopupPaintLock_;

    /// <summary>
    ///
    /// </summary>
    QImage qCefPopupFrame_;

    /// <summary>
    ///
    /// </summary>
    bool isShowingContextMenu_ = false;

    /// <summary>
    ///
    /// </summary>
    QMenu* contextMenu_ = nullptr;

    /// <summary>
    ///
    /// </summary>
    CefRefPtr<CefRunContextMenuCallback> contextMenuCallback_;
  } osr;

  /// <summary>
  /// Native child window private data
  /// </summary>
  struct NcwPrivateData
  {
    /// <summary>
    ///
    /// </summary>
    QCefWindow* qBrowserWindow_ = nullptr;

    /// <summary>
    ///
    /// </summary>
    QWidget* qBrowserWidget_ = nullptr;
  } ncw;

  /// <summary>
  /// The last visited URL
  /// </summary>
  CefString lastUrl_;

#if defined(QT_DEBUG)
  QElapsedTimer paintTimer_;
#endif

    QCefView* view_= nullptr;
public:
  explicit QCefViewPrivate(QCefContextPrivate* ctx,
                           QCefView* view,
                           const QString& url,
                           const QCefSetting* setting = nullptr);

  ~QCefViewPrivate();

  void createCefBrowser(QCefView* view, const QString& url, const QCefSetting* setting);

  void destroyCefBrowser();

  void addLocalFolderResource(const QString& path, const QString& url, int priority = 0);

  void addArchiveResource(const QString& path, const QString& url, const QString& password = "", int priority = 0);

  void setCefWindowFocus(bool focus);

  bool isOSRModeEnabled() const;

    CefBrowserHost::DragOperationsMask OnDragEnter(
            CefRefPtr<CefDragData> drag_data,
            CefMouseEvent ev,
            CefBrowserHost::DragOperationsMask effect);

    CefBrowserHost::DragOperationsMask OnDragOver(
            CefMouseEvent ev,
            CefBrowserHost::DragOperationsMask effect);

    void OnDragLeave();

    CefBrowserHost::DragOperationsMask OnDrop(
            CefMouseEvent ev,
            CefBrowserHost::DragOperationsMask effect);

protected:
  void onCefBrowserCreated(CefRefPtr<CefBrowser> browser, QWindow* window);

  bool onBeforeNewBrowserCreate(qint64 sourceFrameId,
                                const QString& targetUrl,
                                const QString& targetFrameName,
                                QCefView::CefWindowOpenDisposition targetDisposition,
                                QRect rect,
                                QCefSetting settings);

  bool onBeforeNewPopupCreate(qint64 sourceFrameId,
                              const QString& targetUrl,
                              QString& targetFrameName,
                              QCefView::CefWindowOpenDisposition targetDisposition,
                              QRect& rect,
                              QCefSetting& settings,
                              bool& disableJavascriptAccess);

  void onAfterCefPopupCreated(CefRefPtr<CefBrowser> browser);

  void onNewDownloadItem(QSharedPointer<QCefDownloadItem> item, const QString& suggestedName);

  void onUpdateDownloadItem(QSharedPointer<QCefDownloadItem> item);

  bool handleLoadError(CefRefPtr<CefBrowser>& browser,
                       CefRefPtr<CefFrame>& frame,
                       int errorCode,
                       const std::string& errorMsg,
                       const std::string& failedUrl);

  bool requestCloseFromWeb(CefRefPtr<CefBrowser>& browser);

public slots:
  void onAppFocusChanged(QWidget* old, QWidget* now);

  void onViewScreenChanged(QScreen* screen);

  void onCefWindowLostTabFocus(bool next);

  void onCefWindowGotFocus();

  void onCefUpdateCursor(const QCursor& cursor);

  void onCefInputStateChanged(bool editable);

  void onOsrImeCursorRectChanged(const QRect& rc);

  void onOsrShowPopup(bool show);

  void onOsrResizePopup(const QRect& rc);

  void onContextMenuTriggered(QAction* action);

  void onContextMenuDestroyed(QObject* obj);

signals:
  void updateOsrFrame();

protected:
  void onOsrUpdateViewFrame(const QImage& frame, const QRegion& region);

  void onOsrUpdatePopupFrame(const QImage& frame, const QRegion& region);

  void onBeforeCefContextMenu(const MenuBuilder::MenuData& data);

  void onRunCefContextMenu(QPoint pos, CefRefPtr<CefRunContextMenuCallback> callback);

  void onCefContextMenuDismissed();

  void onFileDialog(CefBrowserHost::FileDialogMode mode,
                    const CefString& title,
                    const CefString& default_file_path,
                    const std::vector<CefString>& accept_filters,
                    CefRefPtr<CefFileDialogCallback> callback);

  bool hasDevTools();

  void showDevTools();

  void closeDevTools();

  void StartDragging(CefRefPtr<CefBrowser> browser,
                     CefRefPtr<CefDragData> drag_data,
                     CefRenderHandler::DragOperationsMask allowed_ops,
                     int x,
                     int y);

  void UpdateDragCursor(CefRefPtr<CefBrowser> browser,
                        CefRenderHandler::DragOperation operation);
protected:
  bool eventFilter(QObject* watched, QEvent* event) override;

  QVariant onViewInputMethodQuery(Qt::InputMethodQuery query) const;

  void onViewInputMethodEvent(QInputMethodEvent* event);

  void onViewVisibilityChanged(bool visible);

  void onViewFocusChanged(bool focused);

  void onViewSizeChanged(const QSize& size, const QSize& oldSize);

  void onViewKeyEvent(QKeyEvent* event);

  void onViewMouseEvent(QMouseEvent* event);

  void onViewWheelEvent(QWheelEvent* event);

public:
  int browserId();

  void navigateToString(const QString& content);

  void navigateToUrl(const QString& url);

  bool browserCanGoBack();

  bool browserCanGoForward();

  void browserGoBack();

  void browserGoForward();

  bool browserIsLoading();

  void browserReload();

  void browserStopLoad();

  bool triggerEvent(const QString& name, const QVariantList& args, int64_t frameId = CefViewBrowserClient::MAIN_FRAME);

  bool responseQCefQuery(const QCefQuery& query);

  bool executeJavascript(int64_t frameId, const QString& code, const QString& url);

  bool executeJavascriptWithResult(int64_t frameId, const QString& code, const QString& url, const QString& context);

  void notifyMoveOrResizeStarted();

  bool sendEventNotifyMessage(int64_t frameId, const QString& name, const QVariantList& args);

  bool setPreference(const QString& name, const QVariant& value, const QString& error);
};
